// ==UserScript==		
// @name		Help Forum Tweaks
// @namespace   http://google-help-forum-tweaks.googlecode.com/svn/trunk/help-forum-tweaks.user.js
// @description	Tweaks for the Google Help Forums
// @version	 0.72
// @include	 http://www.google.com/support/forum/p/*
// @include	 http://www.google.de/support/forum/p/*
// @include	 http://www.google.at/support/forum/p/*
// @include	 http://www.google.ch/support/forum/p/*
// @include	 http://www.google.it/support/forum/p/*
// @include	 http://www.google.co.uk/support/forum/p/*
// @include	 http://www.google.es/support/forum/p/*
// ==/UserScript==

/*
Copyright 2009 - 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* Requires Chrome >= 2.x with user-scripts enabled 
** or Mozilla FireFox with Greasemonkey Extensions.
**
** See http://dev.chromium.org/developers/design-documents/user-scripts
** or http://lifehacker.com/5180010/enable-user-scripts-in-google-chrome
**
** Make sure to put it in the right location, eg:
** C:\Documents and Settings\yourname\Local Settings\Application Data\
**	 Google\Chrome\User Data\Default\User Scripts
** and restart Chrome with the option " --enable-user-scripts"
**
** SVN information (please _don't_ remove next line):
** $Id: help-forum-tweaks-r72.user.js 73 2011-03-17 20:21:28Z theylmdl $
**
*/

var URL = window.location.href, ghfConfig = new Object, languageCode = ""; 
var thedomain = window.location.protocol + "//" + window.location.host; // This works for google.at, too, instead of hardwiring google.com.
String.prototype.startsWith = function(s) { return this.indexOf(s) === 0; }

// Google Help Forum view / tweak configuration.
// Feel free to adjust the settings to your personal preferences.
// Remember to restart Chrome when making changes. Firefox requires only a page reload.
function setGHFConfig() {
	// Threads marked as visited in your browser.
	ghfConfig.color_read_by_you	= "#E5E5E5";
	// Threads marked as _not_ visited in your browser.
	ghfConfig.color_read_by_others = "#FFFFFF" ; "#E7EFFF";
	// Title font size. 13px defaults and doesn't hurt the layout, 15px is already large.
	ghfConfig.title_font_size = "13px";
	// "true" or "false" to show thread titles in bold font.
	ghfConfig.thread_title_bold = false;
	// 1 or 0 to show user votes in bold font. Note this is 0/1 to add further options.
	ghfConfig.votes_bold = 1;
	// "true" or "false" to show side links to boards (a menu).
	ghfConfig.show_side_links = true;
	// "true" or "false" to resize the edit field area for posts.
	ghfConfig.resize_editor = true;
	// "true" or "false" to display last post dates as links.
	ghfConfig.link_to_last = true;
	// "true" or "false" to tweak votes with less thumbs up than participants, 
	// applies only to some languages by now.
	ghfConfig.tweak_votes = true;
	// "true" or "false". When true, a permalink will be written under each post beneath "report abuse".
	ghfConfig.show_permalinks = true;
}

// Cookie set and get functions.
if (!GM_getValue && !GM_setValue && !unsafeWindow) {
	function GM_setValue(cookieName, cookieValue, lifeTime) {
		if (!cookieName) { return; }
		if (lifeTime == "delete") { lifeTime = -10; }
		else { lifeTime = 31536000; }
		document.cookie = escape(cookieName) + "=" + escape(cookieValue) +
						  ";expires=" + (new Date((new Date()).getTime() + (1000 * lifeTime))).toGMTString() +
						  ";path=/";
	}

	function GM_getValue(cookieName, oDefault) {
		var cookieJar = document.cookie.split("; ");
		for (var x = 0; x < cookieJar.length; x++) {
			var oneCookie = cookieJar[x].split("=");
			if (oneCookie[0] == escape(cookieName)) {
				try	  { var footm = unescape(oneCookie[1]); } 
				catch(e) { return oDefault; }
				return footm;
			}
		}
		return oDefault;
	}

	function GM_registerMenuCommand(oText, oFunc) {}

	window.unsafeWindow = window;
}

// Show the links in the sidebar
function showSideLinks(serialized_data) {
	var entries = serialized_data.split("\t");
	var side_ul = document.getElementById("wparoot");
	var new_li, new_a, i = 0;
	if (side_ul) {
		for (i=0; i<entries.length; i++) {
			var suburl = entries[i].match(/\"(.*?)\"/);
			var subtitle = entries[i].match(/>(.*?)</);
			var subclass = entries[i].match(/class="(.*?)"/);
			if (!subclass) {
				new_li = document.createElement("li");
				new_li.id = "new_li_" + i;
				new_li.className = "wpabaruhCSS";
				new_a = document.createElement("a");
				new_a.href = suburl[1];
				new_a.innerHTML = subtitle[1];
				new_li.appendChild(new_a);
				side_ul.appendChild(new_li);
			}
		}

		// Add a link to the thread overview on any other page. 
		if (!URL.match("browse")) { 
			var forumName = URL.match("/forum\/p\/([^/]*)[/\?]")[1];
			new_li = document.createElement("li");
			new_li.id = "new_li_" + (i+1);
			new_li.className = "wpabaruhCSS";
			new_a = document.createElement("a");
			new_a.href = thedomain + "/support/forum/p/" + forumName + "/browse?hl=" + languageCode;
			switch (languageCode) {
				case "de": new_a.innerHTML = "Alle Threads"; break;
				case "it": new_a.innerHTML = "Tutte le discussioni"; break;
				case "fr": new_a.innerHTML = "Toutes les discussions"; break;
				case "es": new_a.innerHTML = "Todas las discussiónes"; break;
				default:   new_a.innerHTML = "All Threads"; // default
			}
			new_li.appendChild(new_a);
			side_ul.appendChild(new_li);
		}
	}
}

// Fix editor window: make larger, fix horizontal scrolling
function fixEditorTextField() {
	// Editor is made up of multiple frames, we must fix two of them
	try {
		if (document.getElementById("wmfroot")) {
			// on thread page, change size of editor div to double height
			var newCSS = "div#wpiprde.wpiprdeCSS { height: 300px !important; }";
			GM_addStyle(newCSS);
			// for non-richtext editor users, adjust row count in text area
			var newCSS = "textarea#wpiprdi { height: 300px !important; }";
			GM_addStyle(newCSS);
		} else if (document.body.id=="wpiprde") {
			// in the editor page (iframed), update CSS for fixed word wrap
			var newCSS = 'body#wpiprde.editable { width: 100% !important; }';
			GM_addStyle(newCSS);
		}
	} catch (ex) { }
}

// Checks for votes with less "thumbs up" than participants. Applies only to some languages.
function checkVotes() {
	if ("de" != languageCode && "it" != languageCode && "en" != languageCode) { return; }

	var i = 0, sz = new String;
	var aDiv = document.getElementsByTagName("div");
	for (i = 0; i < aDiv.length; i++) {
		if ("wpcprsCSS" === aDiv[i].className) { 
			if (aDiv[i].childNodes) {
				if (1 < aDiv[i].childNodes.length) {
					tweakVotes(aDiv[i].childNodes[1].childNodes[1]);
				} else {
					tweakVotes(aDiv[i]);
				}
			} 
		}
	}
}

// Modifies the results from the last function.
function tweakVotes(cObj) {
	if (!cObj || !cObj.innerHTML) { return; }
	var sz = cObj.innerHTML, szLinkText = "";
	var rExp = /([0-9]{1,3}) ([^ ]+) ([0-9]{1,3}) /;
	rExp.exec(sz);
	if ((!RegExp.$1 && !RegExp.$3) || RegExp.$1 === RegExp.$3) { return; }

	// Modify the question.
	switch (languageCode) {
		case "de": szLinkText = sz.replace(/Sie auch\?/, "War sie es?"); break;
		case "it": szLinkText = sz.replace(/Anche tu\?/, "Era utile?"); break;
		case "en": szLinkText = sz.replace(/Did you\?/, "Was it?"); break;
		default: return;
	}
	cObj.innerHTML = szLinkText;
}

// Add bottom anchor to page
function jumpToLastReply() {
	try {
		var hash = document.location.hash.toString(), i = nY = nMax = 0, cDiv = newNode = cNewTxt = null, aReplies = new Array();
		if (hash == "#all") {
			// Get us all reply divs.
			for (i = 0; i < document.getElementsByTagName("div").length; i++) {
				cDiv = document.getElementsByTagName("div")[i];
				if (String(cDiv.className).match(/^wpcpraCSS/)) { // Reply's outer div className, id should be fid_(replyid)
					if (String(cDiv.id).match(/^fid_/)) {
						aReplies.push(cDiv);
					}
				}
			}
			if (0 === aReplies.length) { return; } // no replies ;-) .
			cDiv = aReplies.pop(); // Last reply.
			newNode = document.createElement("a");
			newNode.name = newNode.id = "lastreply";
			newNode.href = "#lastreply";
			cDiv.parentNode.insertBefore(newNode, cDiv);
			window.location.hash = "#lastreply"; // Replace the hash mark, this gives correct positions.
		}
	} catch(ex) { } 
}

// Converts the last post's date to a link to this post in the forum overview page. 
// Refactored so links will now a) work with multiple pages threads and b) bring the last reply to the very top.
function addLinkToLastPost() {
	var i = n = nTds = nAnswers = nPages = 0, rows = tds = new Array(), row, tmp1, tmp2, cTD = cChild = null, szThreadURL = szPageArg = "";
	try {
		if (document.getElementById("wlhd")) {
			// traverse table, extract link to post, add to last answer date
			rows = document.getElementsByTagName("tr");
			for (i = 0; i < rows.length; i++) {
				row = rows[i];
				tds = row.getElementsByTagName("td");
				nTds = tds.length;
				if (5 === nTds || 6 === nTds) {
					// get thread URL
					tmp1 = row.innerHTML.match("\/support\/forum\/p\/[a-zA-Z0-9 ]*?\/thread.*?\"");
					if (tmp1) {
						nAnswers = 0;
						cTD = tds[nTds - 2]; // Count of answers - may be 0 or nothing with a known but seldom bug.
						if (cTD.innerHTML && cTD.className.match(/^lscCSS/)) {
							tmp2 = cTD.innerHTML.match(/[0-9]{1,4}/);
							if (tmp2) {
								nAnswers = Number(tmp2); // NaN on error, should not happen due to regexp before. Let's assume 767 :-) 
								if (40 >= nAnswers) {
									szPageArg = ""; // no argument required
								} else {
									nPages = Math.floor(nAnswers / 40); // 19
									if (nAnswers === (nPages * 40)) { nPages--; }; // still 19, 18 with 760
									// page 2 has the argument 'start=40', page 3 'start=80' etc.
									// In this example, we need to add 760 because we have more than 760 replies. 
									szPageArg = "&amp;start=" + (nPages * 40); // page start
								}
							}
						}
						// If there's no reply yet, we don't need a link there - skip it.
						if (0 == nAnswers) { continue; }

						szThreadURL = tmp1[0].substring(0, tmp1[0].length - 1).replace("&amp;","&");
						szThreadURL += szPageArg;
						cTD = tds[nTds - 1];
						// Modified after R 65: Now only the content of the first <div> will be a link, not the whole cell's content (was: invalid).
						for (n = 0; n < cTD.childNodes.length; n++) { // Get first div (date).
							cChild = cTD.childNodes[n];
							if ("DIV" == cChild.tagName || "div" == cChild.tagName) {
								cChild.innerHTML = "<a href=\"" + szThreadURL + "#all\">" + cChild.innerHTML + "</a>";
								break;
							}
						}
					}
				}
			} 
		}
	} catch (ex) { }
}

// This is used to add permalinks to forum posts.
function addPermalinks() {
	var i = 0, j = 0, cDiv = null, cChild = null, fid = "", szURL = "", szBaseURL = "";

	// This function shall only be applied to threads, not overviews. Try to find a reply form textarea to check this.
	if (!document.getElementById("wpiprdi") && !document.getElementById("wpcpfhd")) { return; }

	// Determine the base URL. This should be something like:
	// http://www.google.[com|ccTLD|ccTLD.com|co[m].ccTLD]/support/forum/p/*/thread (thread invoked) where * is the internal forum name
	if (-1 === URL.search(/thread\?/)) { return; } // nothing to be done.
	var aTmp = URL.split("?");
	szBaseURL = aTmp[0]; // I.e., http://www.google.com/support/forum/p/webmasters/thread

	// Walk replies.
	for (i = 0; i < document.getElementsByTagName("div").length; i++) {
		cDiv = document.getElementsByTagName("div")[i];
		if (String(cDiv.className).match(/^wpcpraCSS/)) { // Reply's outer div className, id should be fid_(replyid)
			if (String(cDiv.id).match(/^fid_/)) {
				cChild = null;
				fid = cDiv.id.match(/^fid_(.*)/)[1]; // i.e. '09ceaaf0c6b808fe00049e3adca9c10f'
				if (!fid || 32 !== fid.length) { continue; }
				// We now need a pointer to the child div with the className "wpcprsCSS" (that's the one we need).
				for (j = (i + 1); j < document.getElementsByTagName("div").length; j++) {
					cChild = document.getElementsByTagName("div")[j];
					if ("wpcprsCSS" === cChild.className) { break; } // Got it.
				}
				if (null === cChild) { continue; }
				// We may now create a space and the permalink and add these.
				szURL = szBaseURL + "?fid=" + fid + "&hl=" + languageCode; // This is a functional permalink URI including language code.
				var cSpace = document.createTextNode(" ");
				cChild.appendChild(cSpace);
				// Create new permalink.
				var cNewLnk = document.createElement("a");
				cNewLnk.href = szURL;
				cNewLnk.className = "wpcpirbCSS lCSS"; // Same as "report abuse"
				var cNewTxt = document.createTextNode("Permalink");
				cNewLnk.appendChild(cNewTxt);
				cChild.appendChild(cNewLnk); // Add the permalink.
			}
		}
	}

}

// Workaround for the Chrome implementation of @include, applies to Firefox, too.
// if (URL.startsWith("http://www.google.com/support/forum/p/")) {
if (0 === URL.indexOf("http://www.google.") && -1 != URL.indexOf("/support/forum/p/")) {

	// Determine language code.
	languageCode = URL.match(/[\?&]hl=([^&#]*)/)[1];

	// Read the current user settings.
	setGHFConfig();

	// Inject the CSS styles 
	if (true === ghfConfig.thread_title_bold) {
		GM_addStyle('td.lcnCSS div:first-child a   { font-weight:bold; }');
		GM_addStyle('td.wltbsCSS div:first-child a { font-weight:normal; }');
		GM_addStyle('td.wltbgCSS div:first-child a { font-weight:normal; }');
	}
	if (1 === ghfConfig.votes_bold) {
		GM_addStyle('.wpcprsCSS { font-weight:bold;}');
	}
	if (true === ghfConfig.tweak_votes) { checkVotes(); }

	GM_addStyle('table.lctCSS td div:first-child a { font-size: ' + ghfConfig.title_font_size + '; }');

	GM_addStyle('td.wltbsCSS { background-color: '	  + ghfConfig.color_read_by_you + '; }');
	GM_addStyle('td.wltbsCSS div { background-color: '  + ghfConfig.color_read_by_you + '; }');
	GM_addStyle('td.wltbtcCSS { background-color: '	 + ghfConfig.color_read_by_others + '; }');
	GM_addStyle('td.wltbtcCSS div { background-color: ' + ghfConfig.color_read_by_others + '; }');

	if (true === ghfConfig.resize_editor) { fixEditorTextField(); }

	if (true === ghfConfig.link_to_last) { addLinkToLastPost(); }
  
	if (true === ghfConfig.show_permalinks) { 
		window.addEventListener("load", addPermalinks, false); 
	}

	window.addEventListener("load", jumpToLastReply, false); // Last!

	if (true === ghfConfig.show_side_links) { // inject the board links (menu)
		var forumName = URL.match("/forum\/p\/([^/]*)[/\?]")[1];
		var forumKey = forumName + "_" + languageCode;

		var cachedEntries = "";
		var cachedDate = GM_getValue(forumKey + "_cached", "");
		var currentDate = new Date();
	
		if (cachedDate != "") {
			// check if cache is still valid (24 hrs)
			if (currentDate - Date.parse(cachedDate) < (86400 * 1000)) {
				cachedEntries = GM_getValue(forumKey, "");
			}
		}
		if (cachedEntries != "") { 
			showSideLinks(cachedEntries);
		} else {
			// Fetch sub-forum links from the forum homepage.
			var theurl = thedomain + "/support/forum/p/" + forumName + "?hl=" + languageCode;
			var request = new XMLHttpRequest();
			request.onreadystatechange = function() {
				if ((request.readyState == 4) && (request.status == 200)) {
					var doc = request.responseText;
					var entries = doc.match(/href=.*?lid=.*?</g);
					var serialized = entries.join("\t");
					GM_setValue(forumKey, serialized); // cache data
					GM_setValue(forumKey + "_cached", currentDate.toString());
					showSideLinks(serialized);
				}
			}
			request.open('GET', theurl, true);
			request.send(null);
		}
	}
}

// EOF. 